/************************************************************************
                                                                     
   Xceed Ultimate ListBox for Silverlight                                                                                                                                            
   Copyright (C) 2010 Xceed Software Inc.    
                                                                     
   This program is provided to you under the terms of the GNU General Public  
   License version 2 as published by the Free Software Foundation. 
        
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY, without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with this program, if not, write to the Free Software Foundation, Inc., 
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. Visit Xceed.com to find the commercial edition of 
   Xceed Ultimate ListBox for Silverlight.                                    
                                                                      
 **********************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data.Services.Client;
using System.Data.Services.Common;
using System.Linq;
using Xceed.Silverlight.Data.Stats;

namespace Xceed.Silverlight.Data
{
  public abstract class WcfDataServicesDataSourceProviderBase : AsyncDataSourceProvider, ICountRestrictedProvider
  {
    #region Constructors

    protected WcfDataServicesDataSourceProviderBase( DataServiceContext dataServiceContext )
    {
      if( dataServiceContext == null )
        throw new ArgumentNullException( "dataServiceContext" );

      this.DataServiceContext = dataServiceContext;
    }

    internal WcfDataServicesDataSourceProviderBase()
    {
    }

    #endregion

    #region CanSkip Property

    public override bool CanSkip
    {
      get
      {
        return true;
      }
    }

    #endregion

    #region CanFilter Property

    public override bool CanFilter
    {
      get
      {
        return true;
      }
    }

    #endregion

    #region CanFilterGroup Property

    public sealed override bool CanFilterGroup
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanJump Property

    public sealed override bool CanJump
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanJumpToItem Property

    public sealed override bool CanJumpToItem
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanCount Property

    public override bool CanCount
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanGroupCount Property

    public sealed override bool CanGroupCount
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanCalculateStats Property

    public override bool CanCalculateStats
    {
      get
      {
        return this.CanCount;
      }
    }

    #endregion

    #region InitialItemCount Property

    public Nullable<long> InitialItemCount
    {
      get;
      set;
    }

    #endregion

    #region DataServiceContext Protected Property

    protected DataServiceContext DataServiceContext
    {
      get;
      private set;
    }

    #endregion

    #region IsQueryLengthLimited Internal Property

    internal override bool IsQueryLengthLimited
    {
      get
      {
        return true;
      }
    }

    #endregion

    protected override IEnumerable<SortDescription> GetPrimaryKeyInfo()
    {
      IEnumerable<object> attributes = this.ElementType.GetCustomAttributes( typeof( DataServiceKeyAttribute ), true );
      foreach( object attribute in attributes )
      {
        DataServiceKeyAttribute keyAttribute = ( DataServiceKeyAttribute )attribute;

        foreach( string keyName in keyAttribute.KeyNames )
        {
          string memberName = string.Intern( keyName );

          yield return new SortDescription( memberName, ListSortDirection.Ascending );
        }
      }
    }

    protected override IEnumerable<Type> GetSupportedStatFunctions( Type dataType )
    {
      // Check if we can replace the CountFunction with a call to BeginExecuteCountQuery.
      if( this.CanCount )
      {
        bool mayCount = !dataType.IsNullableType();
        if( ( !mayCount ) && ( this.CanFilter ) )
        {
          // Since the CountFunction may require null values inclusion or exclusion, we need
          // to be able set a filter for null or non-null values.
          List<FilterOperator> filterOperators = this.GetSupportedFilterOperators( dataType ).ToList();

          mayCount = ( filterOperators.Contains( FilterOperator.Equal ) )
                  && ( filterOperators.Contains( FilterOperator.NotEqual ) );
        }

        if( mayCount )
          yield return typeof( CountFunction );
      }

      yield break;
    }

    protected override IAsyncResult BeginExecuteGroupCountQuery(
      GroupCountQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      throw new NotSupportedException();
    }

    protected override GroupCountQueryResult EndExecuteGroupCountQuery( IAsyncResult asyncResult )
    {
      throw new NotSupportedException();
    }

    protected override IAsyncResult BeginExecuteStatQuery(
      StatQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      CountFunction countFunction = queryParameters.Function.StatFunction as CountFunction;

      if( countFunction == null )
        // The StatFunction isn't supported.
        throw new NotSupportedException();

      // The CountFunction may be handled by a call to BeginExecuteCountQuery.
      FilterRequest filterRequest = queryParameters.Filter;
      FilterExpressionBase filterExpression = ( filterRequest != null ) ? filterRequest.Expression : null;
      string propertyName = countFunction.GetSourcePropertyNames().Single();
      bool isNullableType = this.GetElementMemberType( propertyName ).IsNullableType();

      // Modify the filter expression to take care of the null value.
      if( isNullableType )
      {
        if( countFunction.IncludeNullValues )
        {
          FilterExpression includeNull = new FilterExpression( propertyName, FilterOperator.Equal, null );

          if( filterExpression != null )
          {
            filterExpression = new OrFilterExpression( includeNull, filterExpression );
          }
          else
          {
            filterExpression = includeNull;
          }
        }
        else
        {
          FilterExpression excludeNull = new FilterExpression( propertyName, FilterOperator.NotEqual, null );

          if( filterExpression != null )
          {
            filterExpression = new AndFilterExpression( excludeNull, filterExpression );
          }
          else
          {
            filterExpression = excludeNull;
          }
        }
      }

      // Create the new FilterRequest for the count query.
      if( filterRequest != null )
      {
        filterRequest = new FilterRequest( filterRequest.Filter, filterExpression );
      }
      else if( filterExpression != null )
      {
        filterRequest = new FilterRequest( filterExpression );
      }
      else
      {
        filterRequest = null;
      }

      // Start the count query.
      CountQueryParameters newQueryParameters = new CountQueryParameters(
                                                  queryParameters.Sort,
                                                  filterRequest,
                                                  null,
                                                  queryParameters.FilterGroup );

      return this.BeginExecuteCountQuery( newQueryParameters, callback, state );
    }

    protected override StatQueryResult EndExecuteStatQuery( IAsyncResult asyncResult )
    {
      CountQueryResult queryResult = this.EndExecuteCountQuery( asyncResult );

      return new StatQueryResult( new StatResult( queryResult.Count ) );
    }
  }
}
